package com.lalilu.lmedia.extension

import androidx.annotation.StringRes
import com.blankj.utilcode.util.StringUtils
import com.blankj.utilcode.util.TimeUtils
import com.lalilu.lmedia.R
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import java.io.Serializable

/**
 * 排序选择属性规则
 */
enum class SortRule(
    @StringRes
    val titleRes: Int
) {
    /**
     * [Normal]         默认不做排序处理
     * [ItemsCount]     根据容器内元素个数排序
     * [ContentType]    根据自定义的元素类型进行排序
     */
    Normal(R.string.sort_rule_normal),
    Title(R.string.sort_rule_title),
    SubTitle(R.string.sort_rule_sub_title),
    CreateTime(R.string.sort_rule_create_time),
    ModifyTime(R.string.sort_rule_modify_time),
    ItemsCount(R.string.sort_rule_items_count),
    ItemsDuration(R.string.sort_rule_items_duration),
    ContentType(R.string.sort_rule_content_type),
    FileSize(R.string.sort_rule_file_size),
    PlayCount(R.string.sort_rule_play_count),
    LastPlayTime(R.string.sort_rule_last_play_time),
    TrackNumber(R.string.sort_rule_track_number);

    companion object {
        fun from(str: String?, default: SortRule? = null): SortRule {
            str ?: return default ?: Normal
            return SortRule.values().find { it.name == str } ?: default ?: Normal
        }
    }
}

/**
 * 顺序规则
 */
enum class OrderRule(
    @StringRes
    val titleRes: Int
) {
    /**
     * [Normal]        升序排序(默认)
     * [Reverse]       降序排序
     * [Shuffle]    随机洗牌排序
     */
    Normal(R.string.order_rule_normal),
    Reverse(R.string.order_rule_reverse),
    Shuffle(R.string.order_rule_shuffle);

    companion object {
        fun from(str: String?, default: OrderRule? = null): OrderRule {
            str ?: return default ?: Normal
            return OrderRule.values().find { it.name == str } ?: default ?: Normal
        }
    }
}

/**
 * 分组规则
 */
enum class GroupRule(
    @StringRes
    val titleRes: Int
) {
    /**
     * [Normal]                 默认不做分组处理
     * [TitleFirstLetter]       取Title的第一个字符做分组
     * [SubTitleFirstLetter]    取SubTitle的第一个字符做分组
     */
    Normal(R.string.group_rule_normal),
    DiskNumber(R.string.group_rule_disk_number),
    CreateTime(R.string.group_rule_create_time),
    ModifyTime(R.string.group_rule_modify_time),
    TitleFirstLetter(R.string.group_rule_title_first_letter),
    SubTitleFirstLetter(R.string.group_rule_sub_title_first_letter),
    PinYinFirstLetter(R.string.group_rule_pinyin_first_letter);

    companion object {
        fun from(str: String?, default: GroupRule? = null): GroupRule {
            str ?: return default ?: Normal
            return GroupRule.values().find { it.name == str } ?: default ?: Normal
        }
    }
}

interface Sortable {
    companion object {
        const val INVALID_NUM_VALUE = -1L
        const val INVALID_STR_VALUE = "*invalid*"
        const val SORT_FOR_SONGS = "SONGS"
        const val SORT_FOR_ALBUMS = "ALBUMS"
        const val SORT_FOR_ARTISTS = "ARTISTS"
    }

    fun requireTitle(): String = INVALID_STR_VALUE
    fun requireSubTitle(): String = INVALID_STR_VALUE
    fun requireCreateTime(): Long = INVALID_NUM_VALUE
    fun requireModifyTime(): Long = INVALID_NUM_VALUE
    fun requireItemsCount(): Long = INVALID_NUM_VALUE
    fun requireItemsDuration(): Long = INVALID_NUM_VALUE
    fun requireContentType(): String = INVALID_STR_VALUE
    fun requireFileSize(): Long = INVALID_NUM_VALUE
    fun requirePinyinStr(): String = INVALID_STR_VALUE
    fun requireDiskNumber(): Long = INVALID_NUM_VALUE
    fun requireTrackNumber(): Long = INVALID_NUM_VALUE
}

/**
 * sealed class不能直接作为Map的Key，需要实现Serializable接口使其可序列化，否则会报错
 */
sealed class GroupIdentity : Serializable {
    object None : GroupIdentity()
    data class FirstLetter(val letter: String) : GroupIdentity()
    data class DiskNumber(val number: Long) : GroupIdentity()
    data class Time(val time: String) : GroupIdentity()
}

@OptIn(ExperimentalCoroutinesApi::class)
interface SortStrategy<T : Sortable> {
    fun sortBy(rule: SortRule, list: List<T>): List<T>
    fun orderBy(rule: OrderRule, list: List<T>): List<T>
    fun groupBy(rule: GroupRule, list: List<T>): Map<GroupIdentity, List<T>>

    fun sortWithFlow(rule: SortRule, source: Flow<List<T>>): Flow<List<T>> =
        source.mapLatest { sortBy(rule, it) }

    fun orderWithFlow(rule: OrderRule, source: Flow<List<T>>): Flow<List<T>> =
        source.mapLatest { orderBy(rule, it) }

    fun groupWithFlow(rule: GroupRule, source: Flow<List<T>>): Flow<Map<GroupIdentity, List<T>>> =
        source.mapLatest { groupBy(rule, it) }

    private fun Flow<List<T>>.getSortedOutputFlow(
        sortRule: Flow<SortRule>,
        supportSortRules: Flow<List<SortRule>>
    ): Flow<List<T>> = supportSortRules.flatMapLatest supports@{ rules ->
        sortRule.flatMapLatest { rule ->
            if (!rules.contains(rule)) return@flatMapLatest this
            sortWithFlow(rule, this)
        }
    }

    private fun Flow<List<T>>.getOrderedOutputFlow(
        orderRule: Flow<OrderRule>,
        supportOrderRules: Flow<List<OrderRule>>
    ): Flow<List<T>> = supportOrderRules.flatMapLatest supports@{ rules ->
        orderRule.flatMapLatest { rule ->
            if (!rules.contains(rule)) return@flatMapLatest this
            orderWithFlow(rule, this)
        }
    }

    private fun Flow<List<T>>.getGroupedOutputFlow(
        groupRule: Flow<GroupRule>,
        supportGroupRules: Flow<List<GroupRule>>
    ): Flow<Map<GroupIdentity, List<T>>> = supportGroupRules.flatMapLatest supports@{ rules ->
        groupRule.flatMapLatest { rule ->
            if (!rules.contains(rule)) {
                return@flatMapLatest mapLatest { mapOf(GroupIdentity.None to it) }
            }
            groupWithFlow(rule, this)
        }
    }

    fun sortFor(
        inputFlow: Flow<List<T>>,
        sortRuleFlow: Flow<SortRule>,
        orderRuleFlow: Flow<OrderRule>,
        groupRuleFlow: Flow<GroupRule>,
        supportSortRules: Flow<List<SortRule>>,
        supportOrderRules: Flow<List<OrderRule>>,
        supportGroupRules: Flow<List<GroupRule>>,
    ): Flow<Map<GroupIdentity, List<T>>> = inputFlow
        .getSortedOutputFlow(sortRuleFlow, supportSortRules)
        .getOrderedOutputFlow(orderRuleFlow, supportOrderRules)
        .getGroupedOutputFlow(groupRuleFlow, supportGroupRules)
}

open class BaseSortStrategy<T : Sortable> : SortStrategy<T> {
    private val timeStrJustNow: String? =
        StringUtils.getString(R.string.group_identity_time_just_now)
    private val timeStrMinutesAgo: String? =
        StringUtils.getString(R.string.group_identity_time_minutes_ago)
    private val timeStrHoursAgo: String? =
        StringUtils.getString(R.string.group_identity_time_hours_ago)
    private val timeStrExactDay: String? =
        StringUtils.getString(R.string.group_identity_time_exact_day_pattern)

    override fun sortBy(rule: SortRule, list: List<T>): List<T> {
        return when (rule) {
            SortRule.Normal -> list
            SortRule.Title -> list.sortedBy { it.requireTitle() }
            SortRule.SubTitle -> list.sortedBy { it.requireSubTitle() }
            SortRule.ContentType -> list.sortedBy { it.requireContentType() }
            // sort算法默认为从小到大的升序，对于部分数据要求最近的数据在前
            SortRule.CreateTime -> list.sortedByDescending { it.requireCreateTime() }
            SortRule.ModifyTime -> list.sortedByDescending { it.requireModifyTime() }
            SortRule.ItemsCount -> list.sortedByDescending { it.requireItemsCount() }
            SortRule.ItemsDuration -> list.sortedByDescending { it.requireItemsDuration() }
            SortRule.FileSize -> list.sortedByDescending { it.requireFileSize() }
            SortRule.TrackNumber -> list.sortedBy { it.requireDiskNumber() }
                .sortedBy { it.requireTrackNumber() }

            else -> list
        }
    }

    override fun orderBy(rule: OrderRule, list: List<T>): List<T> {
        return when (rule) {
            OrderRule.Normal -> list
            OrderRule.Reverse -> list.reversed()
            OrderRule.Shuffle -> list.shuffled()
        }
    }

    override fun groupBy(rule: GroupRule, list: List<T>): Map<GroupIdentity, List<T>> {
        return when (rule) {
            GroupRule.Normal -> mapOf(GroupIdentity.None to list)
            GroupRule.CreateTime, GroupRule.ModifyTime -> {
                val now = System.currentTimeMillis()
                list.groupBy {
                    val time = it.requireCreateTime() * 1000
                    when {
                        now - time < 300000 -> timeStrJustNow
                        now - time < 3600000 -> timeStrMinutesAgo?.format((now - time) / 60000)
                        now - time < 86400000 -> timeStrHoursAgo?.format((now - time) / 3600000)
                        else -> timeStrExactDay?.let {
                            TimeUtils.millis2String(time, timeStrExactDay)
                        }
                    }
                }.mapKeys { GroupIdentity.Time(it.key ?: "#") }
            }

            GroupRule.DiskNumber -> list
                .groupBy { it.requireDiskNumber() }
                .mapKeys { GroupIdentity.DiskNumber(it.key) }

            GroupRule.TitleFirstLetter -> list
                .groupBy { it.requireTitle().first().uppercase() }
                .mapKeys { GroupIdentity.FirstLetter(it.key) }

            GroupRule.SubTitleFirstLetter -> list
                .groupBy { it.requireSubTitle().first().uppercase() }
                .mapKeys { GroupIdentity.FirstLetter(it.key) }

            GroupRule.PinYinFirstLetter -> list
                .groupBy { it.requirePinyinStr().first().uppercase() }
                .mapKeys { GroupIdentity.FirstLetter(it.key) }
        }
    }
}
